﻿namespace DCHealthCheck {

    #region Usings
    using Contoso.ActiveDirectory;
    using DCHealthCheck.Common;
    using DCHealthCheck.DomainModel;
    using Microsoft.GroupPolicy;
    using Microsoft.Win32;
    using System;
    using System.Collections.Generic;
    using System.DirectoryServices;
    using System.DirectoryServices.ActiveDirectory;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Management;
    using System.Runtime.CompilerServices;
    using System.Security;
    using System.Security.Cryptography.X509Certificates;
    using System.Security.Permissions;
    using System.Security.Principal;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    #endregion

    public class SystemInfoBuilder {

        #region Static fields
        private static log4net.ILog Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private static readonly int LingeringObjectReplicationResultCode = 8606;

        private static readonly int MaxPingLossPct = 60;

        private static readonly uint MaxNegPhaseCorrectionExpectedValue = 172800;

        private static readonly uint MaxPosPhaseCorrectionExpectedValue = 172800;

        private static readonly string NtdsWmiClassName = "Win32_PerfFormattedData_NTDS_NTDS";

        private static readonly string NtdsWmiClassAlternateName = "Win32_PerfFormattedData_DirectoryServices_DirectoryServices";

        private static readonly int RetryTimeoutSeconds = 20;

        private static readonly int RetryLimit = 3;

        private static readonly TimeSpan ReplicationLatencyThreshold = new TimeSpan(90, 0, 0, 0);

        private static TimeSpan TimeDifferenceThreshold = new TimeSpan(0, 4, 50);

        private static readonly TimeSpan TimeSinceLastGoodTimeSyncThreshold = new TimeSpan(3, 0, 0, 0, 0);

        private static readonly TimeSpan WmiSearcherTimeout = new TimeSpan(0, 2, 0);

        #region LDAP ports
        private static readonly int GcPort = 3268;
        private static readonly int GcSSLPort = 3269;
        private static readonly int LdapPort = 389;
        private static readonly int LdapSSLPort = 636;
        #endregion

        #region Wmi Query Filters
        public static readonly string ProcessWmiQueryFilter = "Select ProcessId, ParentProcessId, Priority, CreationDate, CommandLine, ExecutablePath, KernelModeTime, UserModeTime, PageFaults, VirtualSize, WorkingSetSize, PrivatePageCount, QuotaPagedPoolUsage, QuotaNonPagedPoolUsage, HandleCount, ThreadCount, ReadTransferCount, WriteTransferCount, OtherTransferCount from Win32_Process";
        public static readonly string ServiceWmiQueryFilter = "Select DisplayName, ErrorControl, ExitCode, Name, PathName, ProcessId, ServiceType, Started, StartMode, StartName, State, Status from Win32_Service";

        #endregion        #endregion
        #endregion

        #region Members
        private readonly string userName;
        public StringBuilder VerboseLog { get; private set; }

        public SystemInfo SystemInfo {
            get { return systemInfo; }
        }
        private readonly SystemInfo systemInfo;
        private readonly GroupPolicySysvolInfo pdcGroupPolicySysvolInfo;
        private readonly SiteServer siteServer;

        private System.DirectoryServices.ActiveDirectory.DomainController dc;

        internal bool TestStarted { get; private set; }
        internal bool TestCompleted { get; private set; }
        #endregion

        #region Constructors
        private SystemInfoBuilder() {
            this.VerboseLog = new StringBuilder(1048576);
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} (Default Constructor)", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));
        }

        public SystemInfoBuilder(SystemInfo systemInfo)
            : this(systemInfo, null, null) {
        }

        public SystemInfoBuilder(
            SystemInfo systemInfo,
            GroupPolicySysvolInfo pdcGroupPolicySysvolInfo,
            SiteServer siteServer)
            : this() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            #region Validate required dependencies
            if (systemInfo == null) {
                throw new ArgumentNullException("SystemInfo cannot be null");
            }
            #endregion

            this.systemInfo = systemInfo;
            this.systemInfo.DetachedState = DetachedState.Added;
            this.pdcGroupPolicySysvolInfo = pdcGroupPolicySysvolInfo;
            this.siteServer = siteServer;

            try {
                var identity = System.Security.Principal.WindowsIdentity.GetCurrent();
                NTAccount ntAccount = identity.User.Translate(typeof(NTAccount)) as NTAccount;
                userName = ntAccount.Value;
            }
            catch { }
        }
        #endregion

        #region Methods
        public SystemInfo GetSystemInfo() {
            Log.InfoFormat("[ThreadID: {0}] {1} DC Name: {2}", Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;
            this.TestStarted = true;

            try {
                // if we are unable to get an IP address or ping the server, something is 
                // fundamentally wrong that would prevent the remaining tests from working correctly.
                if (!this.GetIpAddress() || !this.PingHost()) {
                    this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} Not performing remaining tests due to GetIPAddress or PingHost returned false.",
                        DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));
                    Log.WarnFormat("[ThreadID: {0}] {1} DC Name: {2} Not performing remaining tests due to GetIPAddress or PingHost returned false.",
                        Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);
                    return null;
                }

                this.GetDCInfo();

                if (this.TestLdapBind()) {
                    this.TestGCBind();

                    if (!userName.ToUpper().Contains("ADM-")) {
                        this.TestLdapSSLBind();
                        this.TestGCSSLBind();
                    }

                    this.GetDomainControllerInfo();
                    this.GetTagObjectValues();
                    this.GetReplicationNeighborInfo();

                    if (this.systemInfo.IsGlobalCatalog) {
                        this.GetGCAdvertisingStatus();
                    }
                }

                this.TestDNS();
                this.GetTimeSyncInfo();
                this.TestTimeServer();
                this.TestSysvol();
                this.GetNTDSDitSize();

                // registry value collection
                this.TestStrictReplication();
                this.GetDNSRegistryValues();
                this.GetSysvolReplicationType();

                // WMI collection
                this.GetSystemWmiInfo();
                this.GetProcessWmiInfo();
                this.GetServiceWmiInfo();
                this.GetProductWmiInfo();
                this.GetRoleFeaturesWmiInfo();
                if (!this.GetNTDSWmiInfo(NtdsWmiClassName)) {
                    this.GetNTDSWmiInfo(NtdsWmiClassAlternateName);
                }

                if (this.systemInfo.ProcessInfos.Count > 0) {
                    this.GetNetstatInfo();
                }

                this.GetCertificates();

                var gpResultTask = this.GetGpResultInfo();
                if (!gpResultTask.Wait(TimeSpan.FromMinutes(5))) {
                    Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} cancelling GetGpResultInfo due to timeout exceeded.",
                        Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, DateTime.UtcNow.Subtract(startTime).ToString());

                    this.systemInfo.GpRsopHtml = null;
                    this.systemInfo.GpRsopXml = null;
                }
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));

                Log.InfoFormat("[ThreadID: {0}] {1} DC: {2} finished.  Time required: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, DateTime.UtcNow.Subtract(startTime).ToString());

                if (!Amib.Threading.SmartThreadPool.IsWorkItemCanceled) {
                    this.TestCompleted = true;
                }
            }

            return this.systemInfo;
        }

        // test and information retrieval methods, in order of processing

        #region Phase 1 Methods
        private bool GetIpAddress() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;
            int retryCount = 0;

            try {
                while (retryCount++ < RetryLimit) {
                    System.Net.IPAddress ipaddress = Utility.GetIpAddress(this.systemInfo.DomainController.DNSName);

                    if (ipaddress != null) {
                        this.systemInfo.IPAddress = ipaddress.ToString();
                        break;
                    }

                    if (retryCount < RetryLimit) {
                        Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Attempt unsuccessful.  Reattempt #: {3} in: {4} seconds",
                            Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, retryCount, RetryTimeoutSeconds);

                        Thread.Sleep(new TimeSpan(0, 0, RetryTimeoutSeconds));
                    }
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));
            }

            return !string.IsNullOrWhiteSpace(this.systemInfo.IPAddress);
        }

        private bool PingHost() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;

            try {
                var pingTestResult = Utility.PingHost(System.Net.IPAddress.Parse(this.systemInfo.IPAddress));

                if ((pingTestResult.RoundTripTimeMs < 0) || (pingTestResult.PercentLoss > MaxPingLossPct)) {
                    this.systemInfo.PingTest = TestResult.Fail;
                }
                else {
                    this.systemInfo.PingTest = TestResult.Success;
                }

                this.systemInfo.PingLossPct = (int)pingTestResult.PercentLoss;
                this.systemInfo.PingRttMs = pingTestResult.RoundTripTimeMs;
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));
            }

            return this.systemInfo.PingTest == TestResult.Success;
        }

        private bool GetDCInfo() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;
            int retryCount = 0;
            var nlTestOutput = new List<string>();
            string commandFileName = "nltest.exe";
            string commandArguments = string.Format(" /server:{0} /dsgetdc:{1}", this.systemInfo.DomainController.DNSName, this.systemInfo.DomainController.DNSDomain);

            try {
                while (retryCount++ < RetryLimit) {
                    nlTestOutput = Utility.ExecuteShellCommand(commandFileName, commandArguments);
                    if ((nlTestOutput != null) && (nlTestOutput.Count > 0)) {
                        string dcLine = nlTestOutput.Where(x => x.Contains(@"DC: \\")).FirstOrDefault();
                        if (dcLine != null) {
                            dcLine = dcLine.Replace(@"DC: \\", string.Empty).Trim();
                            if (string.Equals(dcLine, this.systemInfo.DomainController.DNSName, StringComparison.OrdinalIgnoreCase)) {
                                string flagsLine = nlTestOutput.Where(x => x.Contains("Flags:")).FirstOrDefault();
                                if (flagsLine != null) {
                                    flagsLine = flagsLine.Replace("Flags:", string.Empty).Trim();
                                    string[] flags = flagsLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

                                    if (flags.Length > 0) {
                                        if (flags.Any(x => x == "CLOSE_SITE")) this.systemInfo.DCFlags |= DCFlags.CloseSite;
                                        if (flags.Any(x => x == "DNS_DC")) this.systemInfo.DCFlags |= DCFlags.DNSDC;
                                        if (flags.Any(x => x == "DNS_DOMAIN")) this.systemInfo.DCFlags |= DCFlags.DNSDomain;
                                        if (flags.Any(x => x == "DNS_FOREST")) this.systemInfo.DCFlags |= DCFlags.DNSForest;
                                        if (flags.Any(x => x == "DS")) this.systemInfo.DCFlags |= DCFlags.DS;
                                        if (flags.Any(x => x == "DS_8")) this.systemInfo.DCFlags |= DCFlags.DS_8;
                                        if (flags.Any(x => x == "FULL_SECRET")) this.systemInfo.DCFlags |= DCFlags.FullSecret;
                                        if (flags.Any(x => x == "GC")) this.systemInfo.DCFlags |= DCFlags.GC;
                                        if (flags.Any(x => x == "GTIMESERV")) this.systemInfo.DCFlags |= DCFlags.GTimeServ;
                                        if (flags.Any(x => x == "KDC")) this.systemInfo.DCFlags |= DCFlags.KDC;
                                        if (flags.Any(x => x == "LDAP")) this.systemInfo.DCFlags |= DCFlags.LDAP;
                                        if (flags.Any(x => x == "PDC")) this.systemInfo.DCFlags |= DCFlags.PDC;
                                        if (flags.Any(x => x == "TIMESERV")) this.systemInfo.DCFlags |= DCFlags.TimeServ;
                                        if (flags.Any(x => x == "WRITABLE")) this.systemInfo.DCFlags |= DCFlags.Writable;
                                        if (flags.Any(x => x == "WS")) this.systemInfo.DCFlags |= DCFlags.WS;

                                        if (this.systemInfo.DCFlags != DCFlags.NotSpecified) {
                                            this.SystemInfo.IsGlobalCatalog = this.systemInfo.DCFlags.HasFlag(DCFlags.GC);
                                        }
                                    }
                                    else {
                                        Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} NLTest output Flag count is zero.",
                                            Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);
                                    }

                                    this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} DCInfo Flags: {3}{4}",
                                        DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId,
                                        Utility.CurrentMethodName(), Environment.NewLine, this.SystemInfo.DCFlags.ToString()));

                                    break;

                                } // if (flagsLine != null)
                                else {
                                    Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} NLTest output did not contain 'Flags:'",
                                        Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);
                                }
                            }
                            else {
                                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} NLTest output did not return output for this Domain Controller, but another Domain Controller:",
                                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);

                                Log.WarnFormat("[ThreadID: {0}] {1}   {2}",
                                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), nlTestOutput.ToFormattedString());
                            }
                        } // if (dcLine != null)
                        else {
                            Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} NLTest output did not contain a line for Domain Controller (DC:)",
                                Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);

                            Log.WarnFormat("[ThreadID: {0}] {1}   {2}",
                                Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), nlTestOutput.ToFormattedString());
                        }
                    } // if ((nlTestOutput != null) && (nlTestOutput.Count > 0))

                    if (retryCount < RetryLimit) {
                        Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Attempt unsuccessful.  Reattempt #: {3} in: {4} seconds",
                            Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, retryCount, RetryTimeoutSeconds);

                        Thread.Sleep(new TimeSpan(0, 0, RetryTimeoutSeconds));
                    }
                } // while (retryCount++ < maxRetries)
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));
            }

            return this.SystemInfo.DCFlags != DCFlags.NotSpecified;
        }
        #endregion

        #region Phase 2 Methods
        private bool TestLdapBind() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;
            int retryCount = 0;

            try {
                while (retryCount++ < RetryLimit) {
                    if (!Utility.LdapTest(this.systemInfo.DomainController.DNSName, LdapPort)) {
                        this.systemInfo.LdapBindTest = TestResult.Fail;
                    }
                    else {
                        this.systemInfo.LdapBindTest = TestResult.Success;
                        break;
                    }

                    if (retryCount < RetryLimit) {
                        Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Attempt unsuccessful.  Reattempt #: {3} in: {4} seconds",
                            Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, retryCount, RetryTimeoutSeconds);

                        Thread.Sleep(new TimeSpan(0, 0, RetryTimeoutSeconds));
                    }
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());

                this.systemInfo.ErrorMessageBuilder.AppendLine(string.Format(" -Error: LDAP/Port 389 test: {0}", e.Message));
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));
            }

            return this.systemInfo.LdapBindTest == TestResult.Success;
        }

        private bool TestGCBind() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;
            int retryCount = 0;

            try {
                while (retryCount++ < 3) {
                    if (!Utility.LdapTest(this.systemInfo.DomainController.DNSName, GcPort)) {
                        this.systemInfo.GCBindTest = TestResult.Fail;
                    }
                    else {
                        this.systemInfo.GCBindTest = TestResult.Success;
                        break;
                    }

                    if (retryCount < RetryLimit) {
                        Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Attempt unsuccessful.  Reattempt #: {3} in: {4} seconds",
                            Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, retryCount, RetryTimeoutSeconds);

                        Thread.Sleep(new TimeSpan(0, 0, RetryTimeoutSeconds));
                    }
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));
            }

            return this.systemInfo.GCBindTest == TestResult.Success;
        }

        private bool TestLdapSSLBind() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;
            int retryCount = 0;

            try {
                while (retryCount++ < RetryLimit) {
                    if (!Utility.LdapTest(this.systemInfo.DomainController.DNSName, LdapSSLPort)) {
                        this.systemInfo.LdapSSLBindTest = TestResult.Fail;
                    }
                    else {
                        this.systemInfo.LdapSSLBindTest = TestResult.Success;
                        break;
                    }

                    if (retryCount < RetryLimit) {
                        Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Attempt unsuccessful.  Reattempt #: {3} in: {4} seconds",
                            Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, retryCount, RetryTimeoutSeconds);

                        Thread.Sleep(new TimeSpan(0, 0, RetryTimeoutSeconds));
                    }
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());

                this.systemInfo.ErrorMessageBuilder.AppendLine(string.Format(" -Error: LDAP bind test: {0}", e.Message));
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));
            }

            return this.systemInfo.LdapSSLBindTest == TestResult.Success;
        }

        private bool TestGCSSLBind() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;
            int retryCount = 0;

            try {
                while (retryCount++ < RetryLimit) {
                    if (!Utility.LdapTest(this.systemInfo.DomainController.DNSName, GcSSLPort)) {
                        this.systemInfo.GCSSLBindTest = TestResult.Fail;
                    }
                    else {
                        this.systemInfo.GCSSLBindTest = TestResult.Success;
                        break;
                    }

                    if (retryCount < RetryLimit) {
                        Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Attempt unsuccessful.  Reattempt #: {3} in: {4} seconds",
                            Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, retryCount, RetryTimeoutSeconds);

                        Thread.Sleep(new TimeSpan(0, 0, RetryTimeoutSeconds));
                    }
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());

                this.systemInfo.ErrorMessageBuilder.AppendLine(string.Format(" -Error: LDAP bind test: {0}", e.Message));
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));
            }

            return this.systemInfo.GCSSLBindTest == TestResult.Success;
        }
        #endregion

        #region Phase 3 Methods
        private void GetDomainControllerInfo() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;

            try {
                DirectoryContext directoryContext = new DirectoryContext(DirectoryContextType.DirectoryServer, this.systemInfo.DomainController.DNSName);
                this.dc = System.DirectoryServices.ActiveDirectory.DomainController.GetDomainController(directoryContext);
                if (this.dc != null) {
                    if (this.systemInfo.IPAddress != this.dc.IPAddress) {
                        string message = string.Format("DC: {0} DomainController.IPAddress: {1} does not match resolved IP Address: {2}", this.systemInfo.DomainController.DNSName, this.dc.IPAddress, this.systemInfo.IPAddress);
                        Log.WarnFormat("[ThreadID: {0}] {1} {2}", Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), message);

                        this.systemInfo.ErrorMessageBuilder.AppendLine(string.Format(" -Warning: {0}", message));
                    }

                    this.systemInfo.HighestCommittedUSN = this.dc.HighestCommittedUsn;
                    this.systemInfo.IsGlobalCatalog = this.dc.IsGlobalCatalog();
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));
            }
        }

        /// <summary>
        /// There is a tag object in each domain. Get the value of this object from each dc tested.
        /// </summary>
        private void GetTagObjectValues() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            if (this.dc == null) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Not getting ReplicationNeighbors due to .NET DomainController object is null.",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);
                return;
            }

            var startTime = DateTime.UtcNow;
            string adjustedPath = string.Empty;

            try {
                // the TagObjectValues collection should be pre-populated when the SystemInfo object is created
                if (this.systemInfo.TagObjectValues.Count > 0) {
                    foreach (var tagObjectValue in this.systemInfo.TagObjectValues.ToList()) {
                        string healthCheckTagDN = tagObjectValue.TagObject.DN;

                        adjustedPath = string.Format("GC://{0}/{1}", this.systemInfo.DomainController.DNSName, healthCheckTagDN);
                        string healthCheckTagDomainDN = ADDataAccess.GetDomainDNFromDN(healthCheckTagDN);

                        if (string.Equals(healthCheckTagDomainDN, this.systemInfo.DomainController.DN, StringComparison.OrdinalIgnoreCase)) {
                            adjustedPath = string.Format("LDAP://{0}/{1}", this.systemInfo.DomainController.DNSName, healthCheckTagDN);
                        }

                        if ((!this.systemInfo.IsGlobalCatalog) && adjustedPath.StartsWith("GC")) {
                            Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Not checking Tag object due to DomainController is not a global catalog (possibly due to recent promotion?)",
                                Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);
                            continue;
                        }

                        DateTime tagObjectValueDate = DateTime.MinValue;
                        DateTime searchStartTime = DateTime.UtcNow;
                        int retryCount = 0;

                        SearchResult searchResult = null;

                        while (retryCount++ < RetryLimit) {
                            using (DirectorySearcher directorySearcher = this.dc.GetDirectorySearcher()) {
                                directorySearcher.SearchRoot.Path = adjustedPath;
                                directorySearcher.SearchScope = SearchScope.Base;
                                directorySearcher.ReferralChasing = ReferralChasingOption.None;
                                directorySearcher.CacheResults = false;
                                directorySearcher.Tombstone = false;
                                directorySearcher.Filter = string.Format("distinguishedName={0}", healthCheckTagDN);

                                try {
                                    searchResult = directorySearcher.FindOne();
                                    break;
                                }
                                catch (Exception e) {
                                    Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Tag object: {3} SearchRootPath: {4} Search elapsed time: {5} Exception: {6}",
                                        Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, healthCheckTagDN, adjustedPath,
                                        DateTime.UtcNow.Subtract(searchStartTime).ToString(), e.VerboseExceptionString());

                                    if (!e.Message.Contains("timeout period expired")) break;
                                }
                            } // using (DirectorySearcher directorySearcher = this.DC.GetDirectorySearcher())

                            if (retryCount < RetryLimit) {
                                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Attempt unsuccessful.  Reattempt #: {3} in: {4} seconds",
                                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, retryCount, RetryTimeoutSeconds);

                                Thread.Sleep(new TimeSpan(0, 0, RetryTimeoutSeconds));
                            }
                        } // while (retryCount++ < 3)

                        if (searchResult != null) {
                            if (searchResult.Properties.Contains("Description")) {
                                string description = searchResult.Properties["Description"][0].ToString();

                                if (!string.IsNullOrWhiteSpace(description)) {
                                    if (DateTime.TryParseExact(description, "yyyy-MMM-dd HH:mm", CultureInfo.CurrentCulture.DateTimeFormat, DateTimeStyles.AssumeUniversal, out tagObjectValueDate)) {
                                        tagObjectValue.TagValue = tagObjectValueDate;
                                    }
                                }
                            }
                        }
                    } // for (int index = 0; index < this.systemInfo.TagObjectValues.Count; index++)
                } // if (this.systemInfo.TagObjectValues.Count > 0)
                else {
                    Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} not performing test due to TagObjects are not specified.",
                        Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} SearchRootPath: {3} Exception: {4}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, adjustedPath, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));
            }
        }

        /// <summary>
        /// Get list of ReplicationNeighbors and pending ReplicationOperations
        /// </summary>
        private void GetReplicationNeighborInfo() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            if (this.dc == null) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Not getting ReplicationNeighbors due to .NET DomainController object is null.",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);
                return;
            }

            var startTime = DateTime.UtcNow;

            try {
                #region Get Replication Neighbors
                foreach (System.DirectoryServices.ActiveDirectory.ReplicationNeighbor rn in this.dc.GetAllReplicationNeighbors()) {
                    try {
                        var replicationNeighbor = new DCHealthCheck.DomainModel.ReplicationNeighbor() { DetachedState = DetachedState.Added };
                        var replicationNeighborBuilder = new ReplicationNeighborBuilder(replicationNeighbor, rn, this.siteServer);
                        replicationNeighbor = replicationNeighborBuilder.GetReplicationNeighbor();

                        var validationResults = replicationNeighbor.Validate(null).ToList();
                        if (validationResults.Where(x => x.MemberNames.Any(y => (y != "Id") && (y != "SystemInfo") && (y != "SystemInfoId"))).Count() > 0) {
                            replicationNeighbor = null;

                            Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} ReplicationNeighbor failed validation:",
                                Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);

                            foreach (var result in validationResults) {
                                Log.WarnFormat("[ThreadID: {0}] {1} Property(s): {2} Validation Error Message: {3}",
                                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                                    result.MemberNames.ToList().ToDelimitedString(), result.ErrorMessage);
                            }
                        }
                        else {
                            this.systemInfo.ReplicationNeighbors.Add(replicationNeighbor);
                        }
                    }
                    catch (Exception e) {
                        Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Getting ReplicationNeighborInfo: {3}",
                            Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
                    }
                }

                this.systemInfo.NeighborCount = this.systemInfo.ReplicationNeighbors.Select(x => x.SourceServer).Distinct().ToList().Count;

                if (this.systemInfo.ReplicationNeighbors.Count > 0) {
                    Log.DebugFormat("[ThreadID: {0}] {1} DC: {2} Checking replication status for hosted partitions.",
                        Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);

                    foreach (var partition in this.dc.Partitions) {
                        Log.DebugFormat("[ThreadID: {0}] {1} DC: {2} Partition: {3}",
                            Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, partition.ToString());
                        var partitionNotRecentlyReplicated = true;

                        foreach (var replicationNeighbor in this.systemInfo.ReplicationNeighbors) {
                            if (!string.Equals(replicationNeighbor.PartitionName, partition.ToString(), StringComparison.OrdinalIgnoreCase)) continue;

                            if (replicationNeighbor.LastSuccessfulSyncUTC > DateTime.UtcNow.Subtract(ReplicationLatencyThreshold)) {
                                partitionNotRecentlyReplicated = false;
                            }
                            if (replicationNeighbor.LastSyncResult == LingeringObjectReplicationResultCode) {
                                this.systemInfo.ErrorMessageBuilder.AppendLine(
                                    string.Format(@" -Warning: DC: {0} is reporting failed replication status due to lingering objects for Neighbor: {1} partition: {2}", 
                                    this.SystemInfo.DomainController.DNSName, replicationNeighbor.SourceServer, replicationNeighbor.PartitionName));
                            }
                        }

                        if (partitionNotRecentlyReplicated) {
                            this.systemInfo.ErrorMessageBuilder.AppendLine(
                                    string.Format(@" -Warning: DC: {0} has not recently completed a replication for partition: {1}", this.SystemInfo.DomainController.DNSName, partition));

                        }
                    }
                }
                #endregion

                #region Get Pending Replications
                foreach (System.DirectoryServices.ActiveDirectory.ReplicationOperation ro in this.dc.GetReplicationOperationInformation().PendingOperations) {
                    try {
                        var pendingReplication = new PendingReplication() { DetachedState = DetachedState.Added };
                        var pendingReplicationBuilder = new PendingReplicationBuilder(pendingReplication, ro);
                        pendingReplication = pendingReplicationBuilder.GetPendingReplications();

                        var validationResults = pendingReplication.Validate(null).ToList();
                        if (validationResults.Where(x => x.MemberNames.Any(y => (y != "Id") && (y != "SystemInfo") && (y != "SystemInfoId"))).Count() > 0) {
                            pendingReplication = null;

                            Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} PendingReplication failed validation:",
                                Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);

                            foreach (var result in validationResults) {
                                Log.WarnFormat("[ThreadID: {0}] {1} Property(s): {2} Validation Error Message: {3}",
                                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                                    result.MemberNames.ToList().ToDelimitedString(), result.ErrorMessage);
                            }
                        }
                        else {
                            //pendingReplication.SystemInfo = this.systemInfo;
                            this.systemInfo.PendingReplications.Add(pendingReplication);
                        }
                    }
                    catch (Exception e) {
                        Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Getting ReplicationOperationInfo: {3}",
                            Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
                    }
                }

                this.systemInfo.PendingReplicationCount = this.systemInfo.PendingReplications.Count;
                #endregion
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}.",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));

                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} Replication Neighbors: {3}.  Pending Replication Operations: {4}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    this.systemInfo.ReplicationNeighbors.Count, this.systemInfo.PendingReplications.Count));
            }
        }

        /// <summary>
        /// repadmin may be used to determine if the server is actually advertising as a GC.
        /// </summary>
        private void GetGCAdvertisingStatus() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;
            List<string> repadminOutput = new List<string>();
            string commandFileName = "repadmin.exe";
            string commandArguments = string.Format(" /showreps {0}", this.systemInfo.DomainController.DNSName);

            try {
                // run 'repadmin /showreps dcDnsName'
                // check if it has 'WARNING:  Not advertising as a global cataLog.' in the output
                repadminOutput = Utility.ExecuteShellCommand(commandFileName, commandArguments);
                if ((repadminOutput != null) && (repadminOutput.Count > 0)) {
                    if (repadminOutput.Any(x => x.Contains("WARNING:  Not advertising as a global catalog"))) {
                        this.systemInfo.ErrorMessageBuilder.AppendLine(string.Format(@" -Warning: This server is configured as a global catalog, but repadmin indicates that it is not advertising as a Global CataLog."));
                        this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} Warning: repadmin indicates this server is not advertising as a Global CataLog.  repadmin output:{3}{4}",
                            DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                            Environment.NewLine, repadminOutput.ToFormattedString()));
                    }
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));
            }
        }
        #endregion

        #region Phase 4 Methods
        private void TestDNS() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;
            int retryCount = 0;

            try {
                while (retryCount++ < 3) {
                    if (!Utility.TestNetworkConnection(this.systemInfo.DomainController.DNSName, 53)) {
                        this.systemInfo.DNSTest = TestResult.Fail;
                    }
                    else {
                        this.systemInfo.DNSTest = TestResult.Success;

                        var ipAddresses = Utility.DNSLookup(this.systemInfo.IPAddress, this.systemInfo.DomainController.DNSName);

                        if (ipAddresses != null) {
                            foreach (var ip in ipAddresses) {
                                this.systemInfo.DNSResponse += string.Format("{0},", ip);
                            }

                            while (this.systemInfo.DNSResponse.EndsWith(",")) {
                                this.systemInfo.DNSResponse = this.systemInfo.DNSResponse.Substring(0, this.systemInfo.DNSResponse.Length - 1);
                            }

                            break;
                        }
                    }

                    if (retryCount < RetryLimit) {
                        Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Attempt unsuccessful.  Reattempt #: {3} in: {4} seconds",
                            Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, retryCount, RetryTimeoutSeconds);

                        Thread.Sleep(new TimeSpan(0, 0, RetryTimeoutSeconds));
                    }
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));
            }
        }

        /// <summary>
        /// Runs w32tm.exe /query /computer:dcName /status /verbose and saves the information in a NTPInfo complex type
        /// </summary>
        private void GetTimeSyncInfo() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;

            TimeSyncInfoBuilder timeSyncInfoBuilder = null;
            var timeSyncInfo = new TimeSyncInfo() { DetachedState = DetachedState.Added };

            try {
                if (!string.IsNullOrWhiteSpace(this.systemInfo.DomainController.OSName) && this.systemInfo.DomainController.OSName.Contains("2003")) {
                    NTPClient ntpClient = new NTPClient(this.systemInfo.IPAddress, this.systemInfo.DomainController.DNSName);
                    ntpClient.Connect();

                    if (ntpClient.IsResponseValid) {
                        timeSyncInfoBuilder = new TimeSyncInfoBuilder(timeSyncInfo, ntpClient);
                        timeSyncInfo = timeSyncInfoBuilder.GetTimeSyncInfo();
                    }
                }
                else {
                    timeSyncInfoBuilder = new TimeSyncInfoBuilder(timeSyncInfo, this.systemInfo.DomainController.DNSName);
                    timeSyncInfo = timeSyncInfoBuilder.GetTimeSyncInfo();
                }

                TimeSpan timeSinceLastGoodSync = TimeSpan.Zero;
                if (TimeSpan.TryParse(timeSyncInfo.TimeSinceLastGoodSync, out timeSinceLastGoodSync)) {
                    this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} TimeServerInfo:{3}{4}",
                        DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                        Environment.NewLine, timeSyncInfoBuilder.ToString()));

                    if (timeSinceLastGoodSync > TimeSinceLastGoodTimeSyncThreshold) {
                        this.systemInfo.TimeServerTest = TestResult.Fail;
                        this.systemInfo.ErrorMessageBuilder.AppendLine(string.Format(@" -Warning: Server last synchronized the system time: {0} ago on: {1} (local time)",
                            timeSyncInfo.TimeSinceLastGoodSync, timeSyncInfo.LastSuccessfulSync.ToString("yyyy-MMM-dd HH:mm:ss")));
                    }

                }
                else if (!string.IsNullOrWhiteSpace(timeSyncInfoBuilder.ErrorMessage)) {
                    this.systemInfo.TimeServerTest = TestResult.Fail;
                    this.systemInfo.ErrorMessageBuilder.AppendLine(string.Format(" -Error: Time Server test failed: {0}.", timeSyncInfoBuilder.ErrorMessage));

                    if (timeSyncInfoBuilder.ErrorMessage.Contains("The service has not been started")) {
                        this.systemInfo.ErrorMessageBuilder.AppendLine(string.Format("  -> Action required: Restart the Windows Time Service, wait two minutes, then use the following command to verify it is functioning correctly:"));
                        this.systemInfo.ErrorMessageBuilder.AppendLine(string.Format("  -> 'w32tm /query /computer:{0} /status /verbose'", this.systemInfo.DomainController.DNSName));

                    }
                }

                // Get registry values for W32Time MaxNegPhaseCorrection and MaxPosPhaseCorrection
                // These may be specied as a group policy or service parameter.  Check the GPO setting first.
                object maxNegPhaseCorrectionValue = null;
                object maxPosPhaseCorrectionValue = null;
                uint maxNegPhaseCorrectionActualValue = 0;
                uint maxPosPhaseCorrectionActualValue = 0;

                using (RegistryKey hklmRegistryKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, this.systemInfo.DomainController.DNSName))
                using (RegistryKey w32TimeGpoRegistryKey = hklmRegistryKey.OpenSubKey(@"Software\Policies\Microsoft\W32Time\Config")) {
                    if (w32TimeGpoRegistryKey != null) {
                        maxNegPhaseCorrectionValue = w32TimeGpoRegistryKey.GetValue("MaxNegPhaseCorrection");
                        if (maxNegPhaseCorrectionValue != null) {
                            if (uint.TryParse(maxNegPhaseCorrectionValue.ToString(), out maxNegPhaseCorrectionActualValue)) {
                                if (maxNegPhaseCorrectionActualValue != MaxNegPhaseCorrectionExpectedValue) {
                                    this.systemInfo.ErrorMessageBuilder.AppendLine(string.Format(@" -Warning: W32Time service registry value for setting MaxNegPhaseCorrection: {0} does not match expected value: {1} Registry key: HKLM\Software\Policies\Microsoft\W32Time\Config",
                                        maxNegPhaseCorrectionActualValue, MaxNegPhaseCorrectionExpectedValue));
                                }
                            }
                        }

                        maxPosPhaseCorrectionValue = w32TimeGpoRegistryKey.GetValue("MaxPosPhaseCorrection");
                        if (maxPosPhaseCorrectionValue != null) {
                            if (uint.TryParse(maxPosPhaseCorrectionValue.ToString(), out maxPosPhaseCorrectionActualValue)) {
                                if (maxPosPhaseCorrectionActualValue != MaxPosPhaseCorrectionExpectedValue) {
                                    this.systemInfo.ErrorMessageBuilder.AppendLine(string.Format(@" -Warning: W32Time service registry value for setting MaxPosPhaseCorrection: {0} does not match expected value: {1} Registry key: HKLM\Software\Policies\Microsoft\W32Time\Config",
                                        maxPosPhaseCorrectionActualValue, MaxPosPhaseCorrectionExpectedValue));
                                }
                            }
                        }
                    }
                    else {
                        // check the service configuration
                        using (RegistryKey w32TimeRegistryKey = hklmRegistryKey.OpenSubKey(@"SYSTEM\CurrentControlSet\Services\W32Time\Config")) {
                            if (w32TimeRegistryKey != null) {
                                maxNegPhaseCorrectionValue = w32TimeRegistryKey.GetValue("MaxNegPhaseCorrection");
                                if (maxNegPhaseCorrectionValue != null) {
                                    if (uint.TryParse(maxNegPhaseCorrectionValue.ToString(), out maxNegPhaseCorrectionActualValue)) {
                                        if (maxNegPhaseCorrectionActualValue != MaxNegPhaseCorrectionExpectedValue) {
                                            this.systemInfo.ErrorMessageBuilder.AppendLine(string.Format(@" -Warning: W32Time service registry value for setting MaxNegPhaseCorrection: {0} does not match expected value: {1} Registry key: HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Config",
                                                maxNegPhaseCorrectionActualValue, MaxNegPhaseCorrectionExpectedValue));
                                        }
                                    }
                                }

                                maxPosPhaseCorrectionValue = w32TimeRegistryKey.GetValue("MaxPosPhaseCorrection");
                                if (maxPosPhaseCorrectionValue != null) {
                                    if (uint.TryParse(maxPosPhaseCorrectionValue.ToString(), out maxPosPhaseCorrectionActualValue)) {
                                        if (maxPosPhaseCorrectionActualValue != MaxPosPhaseCorrectionExpectedValue) {
                                            this.systemInfo.ErrorMessageBuilder.AppendLine(string.Format(@" -Warning: W32Time service registry value for setting MaxPosPhaseCorrection: {0} does not match expected value: {1} Registry key: HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Config",
                                                maxPosPhaseCorrectionActualValue, MaxPosPhaseCorrectionExpectedValue));
                                        }
                                    }
                                }
                            }
                            else {
                                this.systemInfo.ErrorMessageBuilder.AppendLine(@" -Warning: W32Time service: unable to query Registry values: HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Config|MaxNegPhaseCorrection and MaxPosPhaseCorrection due to registry key is null.");
                            }
                        }
                    }
                } // using (RegistryKey w32TimeGpoRegistryKey = registryKey.OpenSubKey(@"Software\Policies\Microsoft\W32Time\Config"))

                if (timeSyncInfo != null) {
                    var validationResults = timeSyncInfo.Validate(null).ToList();
                    if (validationResults.Where(x => x.MemberNames.Any(y => (y != "Id") && (y != "SystemInfo") && (y != "SystemInfoId"))).Count() > 0) {
                        this.systemInfo.TimeSyncInfo = null;
                        timeSyncInfo = null;
                        Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} TimeSyncInfo failed validation:",
                            Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);

                        foreach (var result in validationResults) {
                            Log.WarnFormat("[ThreadID: {0}] {1} Property(s): {2} Validation Error Message: {3}",
                                Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                                result.MemberNames.ToList().ToDelimitedString(), result.ErrorMessage);
                        }
                    }
                    else {
                        this.systemInfo.TimeSyncInfo = timeSyncInfo;
                    }
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));
            }
        }

        private bool TestTimeServer() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;

            try {
                int retryCount = 0;

                while (retryCount++ < RetryLimit) {
                    try {
                        NTPClient ntpClient = new NTPClient(this.systemInfo.IPAddress, this.systemInfo.DomainController.DNSName);
                        ntpClient.Connect();

                        if (ntpClient.IsResponseValid) {
                            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} NTP Server response:{3}{4}",
                                DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                                Environment.NewLine, ntpClient.ToString()));

                            if (this.SystemInfo.DCFlags != DCFlags.NotSpecified) {
                                if (this.SystemInfo.DCFlags.HasFlag(DCFlags.TimeServ)) {
                                    this.systemInfo.TimeServerTest = TestResult.Success;
                                    break;
                                }
                                else {
                                    this.systemInfo.TimeServerTest = TestResult.Fail;

                                    this.systemInfo.ErrorMessageBuilder.AppendLine(string.Format(@" -Warning: DC is responding to NTP/Port 123 queries, but is not advertising as a Time Server."));
                                    this.systemInfo.ErrorMessageBuilder.AppendLine(string.Format("  -> Action required: Restart the Windows Time Service."));

                                    break;
                                }
                            }
                            else {
                                this.systemInfo.TimeServerTest = TestResult.Success;
                                break;
                            }
                        } // if (ntpClient.IsResponseValid)
                        else {
                            if (retryCount < RetryLimit) {
                                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Attempt unsuccessful.  Reattempt #: {3} in: {4} seconds",
                                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, retryCount, RetryTimeoutSeconds);

                                Thread.Sleep(new TimeSpan(0, 0, RetryTimeoutSeconds));
                            }
                            else {
                                this.systemInfo.TimeServerTest = TestResult.Fail;

                                this.systemInfo.ErrorMessageBuilder.AppendLine(string.Format(@" -Warning: unable to obtain valid response to NTP time syncronization request"));
                            }
                        }
                    }
                    catch (Exception e) {
                        this.systemInfo.TimeServerTest = TestResult.Fail;

                        Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                            Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());

                        this.systemInfo.ErrorMessageBuilder.AppendLine(string.Format(" -Error: Time Server test failed: {0}", e.Message));

                        if (e.Message.Contains("An existing connection was forcibly closed by the remote host")) {
                            this.systemInfo.ErrorMessageBuilder.AppendLine(string.Format("  -'An existing connection was forcibly closed by the remote host' means the server is not listening on port 123, and is not advertisting as a time server."));
                            this.systemInfo.ErrorMessageBuilder.AppendLine(string.Format("  -> Action required: Restart the Windows Time Service."));
                        }
                    }
                } // while (retryCount++ < retryLimit)
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));
            }

            return this.systemInfo.TimeServerTest == TestResult.Success;
        }
        #endregion

        #region Phase 5 Methods
        /// <summary>
        /// The Sysvol test effectively tests port 445.
        /// If successful, retrieve the Sysvol Canary target GPO folder and get the version info.
        /// </summary>
        private bool TestSysvol() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;
            int retryCount = 0;

            try {
                var groupPolicySysvolInfo = new GroupPolicySysvolInfo() { DetachedState = DetachedState.Added };

                if (this.pdcGroupPolicySysvolInfo != null) {
                    string serverSysvolPath = string.Format(@"\\{0}\sysvol", this.systemInfo.DomainController.DNSName);
                    string sysvolPolicyFoldersPath = string.Format(@"{0}\{1}\Policies", serverSysvolPath, this.systemInfo.DomainController.DNSDomain);
                    string sysvolTestFolderPath = string.Format(@"{0}\{1}", sysvolPolicyFoldersPath, this.pdcGroupPolicySysvolInfo.DirectoryName);

                    while (retryCount++ < 3) {
                        if (!Directory.Exists(serverSysvolPath)) {
                            this.systemInfo.SysvolTest = TestResult.Fail;

                            Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Sysvol path not found: {3}",
                                Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, serverSysvolPath);
                        }
                        else {
                            this.systemInfo.SysvolTest = TestResult.Success;

                            if (!string.IsNullOrWhiteSpace(sysvolTestFolderPath) && Directory.Exists(sysvolTestFolderPath)) {
                                try {
                                    var groupPolicySysvolInfoBuilder = new GroupPolicySysvolInfoBuilder(groupPolicySysvolInfo, pdcGroupPolicySysvolInfo.DisplayName, sysvolTestFolderPath);

                                    groupPolicySysvolInfo = groupPolicySysvolInfoBuilder.GetGroupPolicySysvolInfo();

                                    break;
                                }
                                catch (Exception e) {
                                    Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                                        Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
                                }
                            }
                        } // else (Directory.Exists(sysvolPath))

                        if (retryCount < RetryLimit) {
                            Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Attempt unsuccessful.  Reattempt #: {3} in: {4} seconds",
                                Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, retryCount, RetryTimeoutSeconds);

                            Thread.Sleep(new TimeSpan(0, 0, RetryTimeoutSeconds));
                        }
                    } // while (retryCount++ < 3)
                } // if (!string.IsNullOrWhiteSpace(this.SysvolTestFolderName))
                else {
                    Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Not performing test due to SysvolTestFolderName is not specified.",
                        Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);
                }

                if (groupPolicySysvolInfo != null) {
                    var validationResults = groupPolicySysvolInfo.Validate(null).ToList();
                    if (validationResults.Where(x => x.MemberNames.Any(y => (y != "Id") && (y != "SystemInfo") && (y != "SystemInfoId"))).Count() > 0) {
                        this.systemInfo.GroupPolicySysvolInfo = null;
                        groupPolicySysvolInfo = null;

                        Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} GroupPolicySysvolInfo failed validation:",
                            Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);

                        foreach (var result in validationResults) {
                            Log.WarnFormat("[ThreadID: {0}] {1} Property(s): {2} Validation Error Message: {3}",
                                Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                                result.MemberNames.ToList().ToDelimitedString(), result.ErrorMessage);
                        }
                    }
                    else {
                        this.systemInfo.GroupPolicySysvolInfo = groupPolicySysvolInfo;
                    }
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));
            }

            return this.systemInfo.SysvolTest == TestResult.Success;
        }

        private bool GetNTDSDitSize() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;
            bool success = false;

            try {
                string ntdsDitFolder = string.Format(@"\\{0}\C$\Windows\NTDS", this.systemInfo.DomainController.DNSName);
                if (!Directory.Exists(ntdsDitFolder)) {
                    ntdsDitFolder = string.Format(@"\\{0}\D$\NTDS", this.systemInfo.DomainController.DNSName);
                }
                string ntdsDitPath = Path.Combine(ntdsDitFolder, "ntds.dit");

                var fileInfo = new FileInfo(ntdsDitPath);
                this.systemInfo.NTDSDitSizeMB = (int)(fileInfo.Length / 1048576);

                success = true;
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));
            }

            return success;
        }

        /// <summary>
        /// Test for StrictReplication 
        /// Registry Value: Strict Replication Consistency  
        /// Registry Key: HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Parameters\
        /// http://technet.microsoft.com/en-us/library/cc816938%28v=ws.10%29.aspx
        /// </summary>
        /// <remarks>This is the default for new domains, however pre-existing Windows 2003 domains did not have this setting, and it may have been 
        /// carried forward when the domain was upgraded to Windows 2008.
        /// </remarks>
        private bool TestStrictReplication() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;
            int retryCount = 0;

            try {
                while (retryCount++ < 3) {
                    using (var registryKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, this.systemInfo.DomainController.DNSName))
                    using (var ntdsRegistryKey = registryKey.OpenSubKey(@"System\CurrentControlSet\Services\NTDS\Parameters")) {
                        object strictReplicationRegistryValue = ntdsRegistryKey.GetValue("Strict Replication Consistency");

                        if (strictReplicationRegistryValue != null) {
                            int strictReplicationValue = 0;
                            if (int.TryParse(strictReplicationRegistryValue.ToString(), out strictReplicationValue) && (strictReplicationValue == 1)) {
                                this.systemInfo.StrictReplicationTest = TestResult.Success;
                            }
                            else {
                                this.systemInfo.StrictReplicationTest = TestResult.Fail;
                                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} 'Strict Replication Consistency' Registry Value: {3} should be '1'",
                                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, strictReplicationRegistryValue.ToString());
                            }

                            break;
                        }
                        else {
                            if (retryCount < RetryLimit) {
                                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Attempt unsuccessful.  Reattempt #: {3} in: {4} seconds",
                                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, retryCount, RetryTimeoutSeconds);

                                Thread.Sleep(new TimeSpan(0, 0, RetryTimeoutSeconds));
                            }
                            else {
                                this.systemInfo.StrictReplicationTest = TestResult.Fail;
                                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} 'Strict Replication Consistency' Registry Value not found.",
                                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);
                            }
                        }
                    } // using (RegistryKey ntdsRegistryKey = registryKey.OpenSubKey(@"System\CurrentControlSet\Services\NTDS\Parameters"))
                } // while (retryCount++ < 3)
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));
            }

            return this.systemInfo.StrictReplicationTest == TestResult.Success;
        }

        /// <summary>
        /// Get PublishAddresses, ListenAddresses, and ScavengingInterval registry values of DNS Server
        /// </summary>
        private void GetDNSRegistryValues() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;

            try {
                using (var registryKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, this.systemInfo.DomainController.DNSName))
                using (var dnsRegistryKey = registryKey.OpenSubKey(@"System\CurrentControlSet\Services\DNS\Parameters")) {
                    if (dnsRegistryKey != null) {
                        object listenAddressesRegistryValue = dnsRegistryKey.GetValue("ListenAddresses");

                        if (listenAddressesRegistryValue != null) {
                            string[] listenAddressesArray = listenAddressesRegistryValue as string[];

                            if (listenAddressesArray != null) {
                                foreach (var ipAddress in listenAddressesArray) {
                                    this.systemInfo.DNSListenAddresses += string.Format("{0},", ipAddress);
                                }
                            }

                            while (this.systemInfo.DNSListenAddresses.EndsWith(",")) {
                                this.systemInfo.DNSListenAddresses = this.systemInfo.DNSListenAddresses.Substring(0, this.systemInfo.DNSListenAddresses.Length - 1);
                            }

                            this.systemInfo.DNSListenAddresses = this.systemInfo.DNSListenAddresses.Replace(",,", ",");
                        }

                        object publishAddressesRegistryValue = dnsRegistryKey.GetValue("PublishAddresses");

                        if (publishAddressesRegistryValue != null) {
                            this.systemInfo.DNSPublishAddresses = publishAddressesRegistryValue.ToString();
                        }

                        object scavengingIntervalRegistryValue = dnsRegistryKey.GetValue("ScavengingInterval");

                        if (scavengingIntervalRegistryValue != null) {
                            int dnsScavengingInterval = 0;
                            if (int.TryParse(scavengingIntervalRegistryValue.ToString(), out dnsScavengingInterval)) {
                                this.systemInfo.DNSScavengingInterval = dnsScavengingInterval;
                            }
                        }
                    } // if (dnsRegistryKey != null)
                } // using (RegistryKey dnsRegistryKey = registryKey.OpenSubKey(@"System\CurrentControlSet\Services\DNS\Parameters"))
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));
            }
        }

        /// <summary>
        /// Checks for the following registry value to determine if FRS or DFSR is used for replicating Sysvol
        /// HKLM\System\CurrentControlSet\Services\DFSR\Parameters\SysVols\Migrating Sysvols\LocalState
        /// http://msdn.microsoft.com/en-us/library/windows/desktop/cc507518%28v=vs.85%29.aspx#determining_whether_a_domain_controller_s_sysvol_folder_is_replicated_by_dfsr_or_frs
        /// </summary>
        /// <returns></returns>
        private void GetSysvolReplicationType() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;

            try {
                if (!string.IsNullOrWhiteSpace(this.systemInfo.DomainController.OSName) && this.systemInfo.DomainController.OSName.Contains("2012")) {
                    this.systemInfo.SysvolReplicationType = SysvolReplicationType.DFSR;
                    this.systemInfo.SysvolMigrationState = SysvolMigrationState.Eliminated;
                }
                else {
                    using (var registryKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, this.systemInfo.DomainController.DNSName))
                    using (var dfsrRegistryKey = registryKey.OpenSubKey(@"System\CurrentControlSet\Services\DFSR\Parameters\SysVols\Migrating Sysvols")) {
                        if (dfsrRegistryKey != null) {
                            object localStateValue = dfsrRegistryKey.GetValue("Local State");

                            var sysvolMigrationState = SysvolMigrationState.NotSpecified;
                            if (Enum.TryParse<SysvolMigrationState>(localStateValue.ToString(), true, out sysvolMigrationState)) {
                                this.systemInfo.SysvolMigrationState = sysvolMigrationState;
                                if (sysvolMigrationState == SysvolMigrationState.Eliminated) {
                                    this.systemInfo.SysvolReplicationType = SysvolReplicationType.DFSR;
                                }
                                else {
                                    this.systemInfo.SysvolReplicationType = SysvolReplicationType.FRS;
                                }
                            }
                        }
                        else {
                            // registry key does not exist
                            this.systemInfo.SysvolReplicationType = SysvolReplicationType.FRS;
                        }
                    } // using (RegistryKey dfsrRegistryKey = registryKey.OpenSubKey(@"System\CurrentControlSet\Services\DFSR\Parameters"))
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3} Sysvol Replication Type: {4}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString(), this.systemInfo.SysvolReplicationType));
            }
        }
        #endregion

        #region Phase 6 Methods
        [MethodImpl(MethodImplOptions.NoOptimization)]
        private void GetSystemWmiInfo() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;

            try {
                var systemWmiInfoBuilder = new SystemWmiInfoBuilder(this.systemInfo);
                systemWmiInfoBuilder.GetSystemWmiInfo();
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString()));
            }
        }

        private void GetProcessWmiInfo() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;

            try {
                var scope = new ManagementScope(string.Format(@"\\{0}\root\CIMV2", this.systemInfo.DomainController.DNSName));
                var query = new ObjectQuery(ProcessWmiQueryFilter);
                using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query)) {
                    searcher.Options.Timeout = WmiSearcherTimeout;
                    // perform two passes to compensate for the scenario where a process/service may have been momentarily stopped or is restarting
                    for (int index = 0; index < 2; index++) {
                        foreach (ManagementObject process in searcher.Get()) {
                            try {
                                var processInfo = new ProcessInfo() { DetachedState = DetachedState.Added };
                                var processInfoBuilder = new ProcessInfoBuilder(processInfo, process, this.systemInfo.DCTime);
                                processInfo = processInfoBuilder.GetProcessInfo();

                                if (!this.systemInfo.ProcessInfos.Any(x => x.ProcessId == processInfo.ProcessId)) {
                                    var validationResults = processInfo.Validate(null).ToList();
                                    if (validationResults.Where(x => x.MemberNames.Any(y => (y != "Id") && (y != "SystemInfo") && (y != "SystemInfoId"))).Count() > 0) {
                                        processInfo = null;

                                        Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} ProcessInfo failed validation:",
                                            Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);

                                        foreach (var result in validationResults) {
                                            Log.WarnFormat("[ThreadID: {0}] {1} Property(s): {2} Validation Error Message: {3}",
                                                Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                                                result.MemberNames.ToList().ToDelimitedString(), result.ErrorMessage);
                                        }
                                    }
                                    else {
                                        this.systemInfo.ProcessInfos.Add(processInfo);
                                    }
                                }
                            }
                            catch (Exception e) {
                                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
                            }
                        } // foreach (ManagementObject process in searcher.Get())

                        Thread.Sleep(new TimeSpan(0, 0, RetryTimeoutSeconds));

                    } // for (int index = 0; index < 2; index++)
                } // using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query))

                if (this.systemInfo.ProcessInfos.Count > 0) {
                    this.systemInfo.TotalHandles = (int)this.systemInfo.ProcessInfos.Sum(x => x.HandleCount);
                    this.systemInfo.TotalThreads = (int)this.systemInfo.ProcessInfos.Sum(x => x.ThreadCount);
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3} Processes: {4}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString(), this.systemInfo.ProcessInfos.Count));
            }
        }

        private void GetServiceWmiInfo() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;

            try {
                var scope = new ManagementScope(string.Format(@"\\{0}\root\CIMV2", this.systemInfo.DomainController.DNSName));
                var query = new ObjectQuery(ServiceWmiQueryFilter);
                using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query)) {
                    searcher.Options.Timeout = WmiSearcherTimeout;

                    foreach (ManagementObject service in searcher.Get()) {
                        try {
                            var serviceInfo = new ServiceInfo() { DetachedState = DetachedState.Added };
                            var serviceInfoBuilder = new ServiceInfoBuilder(serviceInfo, service);
                            serviceInfo = serviceInfoBuilder.GetServiceInfo();

                            var validationResults = serviceInfo.Validate(null).ToList();
                            if (validationResults.Where(x => x.MemberNames.Any(y => (y != "Id") && (y != "SystemInfo") && (y != "SystemInfoId"))).Count() > 0) {
                                serviceInfo = null;

                                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} ServiceInfo failed validation:",
                                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);

                                foreach (var result in validationResults) {
                                    Log.WarnFormat("[ThreadID: {0}] {1} Property(s): {2} Validation Error Message: {3}",
                                        Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                                        result.MemberNames.ToList().ToDelimitedString(), result.ErrorMessage);
                                }
                            }
                            else {
                                this.systemInfo.ServiceInfos.Add(serviceInfo);
                            }
                        }
                        catch (Exception e) {
                            Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                                Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
                        }
                    } // foreach (ManagementObject process in searcher.Get())
                } // using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query))
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3} Services: {4}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString(), this.systemInfo.ServiceInfos.Count));
            }
        }

        private void GetProductWmiInfo() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            // do not perform check for Windows Server 2003, as we did not install the MSI WMI Provider
            if (string.IsNullOrWhiteSpace(this.systemInfo.DomainController.OSName) || this.systemInfo.DomainController.OSName.Contains("2003")) return;

            var startTime = DateTime.UtcNow;
            try {
                var scope = new ManagementScope(string.Format(@"\\{0}\root\CIMV2", this.systemInfo.DomainController.DNSName));
                var query = new ObjectQuery("Select * from Win32_Product");
                using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query)) {
                    searcher.Options.Timeout = WmiSearcherTimeout;
                    foreach (ManagementObject product in searcher.Get()) {
                        try {
                            var installedProduct = new InstalledProduct() { DetachedState = DetachedState.Added };
                            var installedProductBuilder = new InstalledProductBuilder(installedProduct, product);
                            installedProduct = installedProductBuilder.GetInstalledProduct();

                            var validationResults = installedProduct.Validate(null).ToList();
                            if (validationResults.Where(x => x.MemberNames.Any(y => (y != "Id") && (y != "SystemInfo") && (y != "SystemInfoId"))).Count() > 0) {
                                installedProduct = null;

                                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} InstalledProduct failed validation:",
                                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);

                                foreach (var result in validationResults) {
                                    Log.WarnFormat("[ThreadID: {0}] {1} Property(s): {2} Validation Error Message: {3}",
                                        Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                                        result.MemberNames.ToList().ToDelimitedString(), result.ErrorMessage);
                                }
                            }
                            else {
                                this.systemInfo.InstalledProducts.Add(installedProduct);
                            }
                        }
                        catch (Exception e) {
                            Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                                Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
                        }
                    } // foreach (ManagementObject product in searcher.Get())
                } // using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query))
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3} Products found: {4}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString(), this.systemInfo.InstalledProducts.Count));
            }
        }

        /// <summary>
        /// Server roles and features
        /// http://msdn.microsoft.com/en-us/library/cc280268%28VS.85%29.aspx
        /// </summary>
        private void GetRoleFeaturesWmiInfo() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            if (string.IsNullOrWhiteSpace(this.systemInfo.DomainController.OSName) || this.systemInfo.DomainController.OSName.Contains("2003")) return;

            var startTime = DateTime.UtcNow;

            try {
                var scope = new ManagementScope(string.Format(@"\\{0}\root\CIMV2", this.systemInfo.DomainController.DNSName));
                var query = new ObjectQuery("Select * from Win32_ServerFeature");
                using (var searcher = new ManagementObjectSearcher(scope, query)) {
                    searcher.Options.Timeout = WmiSearcherTimeout;
                    foreach (ManagementObject wmiObject in searcher.Get()) {
                        try {
                            var roleFeature = new RoleFeature() { DetachedState = DetachedState.Added };
                            var roleFeatureBuilder = new RoleFeatureBuilder(roleFeature, wmiObject);
                            roleFeature = roleFeatureBuilder.GetInstance();

                            var validationResults = roleFeature.Validate(null).ToList();
                            if (validationResults.Where(x => x.MemberNames.Any(y => (y != "Id") && (y != "SystemInfo") && (y != "SystemInfoId"))).Count() > 0) {
                                roleFeature = null;

                                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} RoleFeature failed validation:",
                                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);

                                foreach (var result in validationResults) {
                                    Log.WarnFormat("[ThreadID: {0}] {1} Property(s): {2} Validation Error Message: {3}",
                                        Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                                        result.MemberNames.ToList().ToDelimitedString(), result.ErrorMessage);
                                }
                            }
                            else {
                                this.systemInfo.RoleFeatures.Add(roleFeature);
                            }
                        }
                        catch (Exception e) {
                            Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                                Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
                        }
                    } // foreach (ManagementObject product in searcher.Get())
                } // using (var searcher = new ManagementObjectSearcher(scope, query))
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3} Products found: {4}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString(), this.systemInfo.InstalledProducts.Count));
            }
        }

        private bool GetNTDSWmiInfo(string wmiClassName) {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;
            bool success = false;

            if (!string.IsNullOrWhiteSpace(this.systemInfo.DomainController.OSName) && !this.systemInfo.DomainController.OSName.Contains("2003")) {
                try {
                    string queryFilter = string.Format("Select DRAInboundBytesTotalSinceBoot, DRAOutboundBytesTotalSinceBoot from {0}", wmiClassName);
                    var scope = new ManagementScope(string.Format(@"\\{0}\root\CIMV2", this.systemInfo.DomainController.DNSName));
                    var query = new ObjectQuery(queryFilter);
                    using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query)) {
                        searcher.Options.Timeout = WmiSearcherTimeout;
                        foreach (ManagementObject systemProperty in searcher.Get()) {
                            if (systemProperty["DRAInboundBytesTotalSinceBoot"] != null) this.systemInfo.RepInMB = (int)(Convert.ToInt64(systemProperty["DRAInboundBytesTotalSinceBoot"]) / 1048576);
                            if (systemProperty["DRAOutboundBytesTotalSinceBoot"] != null) this.systemInfo.RepOutMB = (int)(Convert.ToInt64(systemProperty["DRAOutboundBytesTotalSinceBoot"]) / 1048676);

                            success = true;
                            break;
                        }
                    }
                }
                catch (Exception e) {
                    if (!e.Message.Contains("Invalid class")) {
                        Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                            Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
                    }
                }
                finally {
                    this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                        DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                        DateTime.UtcNow.Subtract(startTime).ToString()));
                }
            }
            else {
                success = true;
            }

            return success;
        }

        private void GetNetstatInfo() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;

            try {
                var netstatOutput = Utility.ExecuteRemoteShellCommand(this.systemInfo.DomainController.DNSName, "netstat.exe -ano");

                foreach (var line in netstatOutput) {
                    var netstatInfo = new NetstatInfo(line);
                    netstatInfo.DetachedState = DetachedState.Added;

                    var validationResults = netstatInfo.Validate(null).ToList();

                    if (validationResults.Where(x => x.MemberNames.Any(y => (y != "Id") && (y != "SystemInfo") && (y != "SystemInfoId"))).Count() > 0) {
                        netstatInfo = null;
                    }
                    else {
                        this.systemInfo.NetstatInfos.Add(netstatInfo);

                        var processInfo = this.systemInfo.ProcessInfos.Where(x => x.ProcessId == netstatInfo.ProcessId).FirstOrDefault();

                        if (processInfo != null) {
                            netstatInfo.ProcessInfo = processInfo;
                        }
                        else {
                            Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} ProcessInfo not found for NetstatInfo: {3}",
                                Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, netstatInfo.ToString());
                        }
                    }
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            finally {
                this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3} Netstat connections: {4}",
                    DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                    DateTime.UtcNow.Subtract(startTime).ToString(), this.systemInfo.NetstatInfos.Count));
            }
        }

        private async Task GetGpResultInfo() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));

            var startTime = DateTime.UtcNow;
            string htmlReport = null;
            string xmlReport = null;

            await Task.Run(() => {
                try {
                    using (Microsoft.GroupPolicy.GPRsop gpRsop = new GPRsop(RsopMode.Logging, string.Empty)) {
                        GPStatusMessageCollection gpStatusMessages = null;

                        gpRsop.LoggingComputer = this.systemInfo.DomainController.DNSName;
                        gpRsop.LoggingMode = LoggingMode.Computer;

                        gpRsop.CreateQueryResults();
                        htmlReport = gpRsop.GenerateReport(ReportType.Html, out gpStatusMessages);
                        if (!string.IsNullOrWhiteSpace(htmlReport)) {
                            this.systemInfo.GpRsopHtml = htmlReport.ToCompressedByteArray();
                        }

                        if (gpStatusMessages != null) {
                            foreach (GPStatusMessage gpStatusMessage in gpStatusMessages) {
                                Log.WarnFormat("Message: {0}", gpStatusMessage.Message);
                            }
                        }

                        xmlReport = gpRsop.GenerateReport(ReportType.Xml, out gpStatusMessages);
                        if (!string.IsNullOrWhiteSpace(xmlReport)) {
                            this.systemInfo.GpRsopXml = xmlReport.ToCompressedByteArray();
                        }

                        if (gpStatusMessages != null) {
                            foreach (GPStatusMessage gpStatusMessage in gpStatusMessages) {
                                Log.WarnFormat("Message: {0}", gpStatusMessage.Message);
                            }
                        }

                        gpRsop.ReleaseQueryResults();
                    } // using (Microsoft.GroupPolicy.GPRsop gpRsop = new GPRsop(RsopMode.Logging, string.Empty))
                }
                catch (Exception e) {
                    Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                        Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
                }
                finally {
                    this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                        DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                        DateTime.UtcNow.Subtract(startTime).ToString()));
                }
            });
        }

        [SecurityCritical]
        [StorePermission(SecurityAction.Demand, OpenStore = true, EnumerateCertificates = true)]
        private void GetCertificates() {
            this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2}", DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName()));
            var startTime = DateTime.UtcNow;
                try {
                    var keyStore = new X509Store(string.Format(@"\\{0}\My", this.systemInfo.DomainController.DNSName), StoreLocation.LocalMachine);
                    if (keyStore == null) {
                        Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Null KeyStore returned.",
                            Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);
                    }
                    else {
                        keyStore.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);

                        foreach (X509Certificate2 x509cert in keyStore.Certificates) {
                            if (x509cert == null) continue;

                            var certificate = new Certificate() { DetachedState = DetachedState.Added };
                            certificate.X509CertificateRawData = x509cert.RawData;
                            certificate.FriendlyName = x509cert.FriendlyName;
                            certificate.EffectiveDate = x509cert.NotBefore;
                            certificate.ExpirationDate = x509cert.NotAfter;
                            certificate.IssuerName = x509cert.IssuerName.Name;
                            certificate.PublicKey = x509cert.GetPublicKeyString();
                            certificate.SerialNumber = x509cert.SerialNumber;
                            certificate.SubjectName = x509cert.SubjectName.Name;
                            certificate.Thumbprint = x509cert.Thumbprint;

                            var validationResults = certificate.Validate(null).ToList();
                            if (validationResults.Where(x => x.MemberNames.Any(y => (y != "Id") && (y != "SystemInfo") && (y != "SystemInfoId"))).Count() > 0) {
                                certificate = null;

                                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Certificate failed validation:",
                                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName);

                                foreach (var result in validationResults) {
                                    Log.WarnFormat("[ThreadID: {0}] {1} Property(s): {2} Validation Error Message: {3}",
                                        Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                                        result.MemberNames.ToList().ToDelimitedString(), result.ErrorMessage);
                                }
                            }
                            else {
                                this.systemInfo.Certificates.Add(certificate);
                            }
                        }
                    }
                }
                catch (Exception e) {
                    Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Exception: {3}",
                        Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.systemInfo.DomainController.DNSName, e.VerboseExceptionString());
                }
                finally {
                    this.VerboseLog.AppendLine(string.Format("{0} [ThreadID: {1}] {2} finished.  Time required: {3}",
                        DateTime.Now.TimeOfDay.ToString(), Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(),
                        DateTime.UtcNow.Subtract(startTime).ToString()));
                }
            
        }
        #endregion

        public override string ToString() {
            return this.systemInfo.DomainController.DNSName;
        }
        #endregion
    }
}
